/*
 * Copyright (c) 2010 Philip Foulkes <philip.foulkes@gmail.com>
 *
 * This file is free software and is made available under the
 * terms of the GNU General Public License, version 2, or at your
 * option, any later version
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/hardirq.h>

#include "mrp_private.h"
#include "mrp_timer.h"
#include "mrp_state_machine.h"
#include "mrp_attribute.h"

#include "mrp_registrar.h"

/**
*    Registrar state table
*/
static const int mrp_registrar_state_table [MRP_PROTO_EVENT_NUM] [MRP_REGISTRAR_STATE_NUM] =
{
    [MRP_PROTO_EVENT_Begin]
        = { MRP_STATE_MACHINE_SET_UP_STATE_1 (MRP_REGISTRAR_STATE_MT),
            MRP_STATE_MACHINE_SET_UP_STATE_1 (MRP_REGISTRAR_STATE_MT),
            MRP_STATE_MACHINE_SET_UP_STATE_1 (MRP_REGISTRAR_STATE_MT) },
    [MRP_PROTO_EVENT_rNew]
        = { MRP_STATE_MACHINE_SET_UP_STATE_2 (MRP_REGISTRAR_STATE_IN, MRP_PROTO_ACTION_New),
            MRP_STATE_MACHINE_SET_UP_STATE_3 (MRP_REGISTRAR_STATE_IN, MRP_PROTO_ACTION_New, MRP_PROTO_ACTION_STOP_leavetimer),
            MRP_STATE_MACHINE_SET_UP_STATE_2 (MRP_REGISTRAR_STATE_IN, MRP_PROTO_ACTION_New) },
    [MRP_PROTO_EVENT_rJoinIn]
        = { MRP_STATE_MACHINE_SET_UP_STATE_1 (MRP_REGISTRAR_STATE_IN),
            MRP_STATE_MACHINE_SET_UP_STATE_2 (MRP_REGISTRAR_STATE_IN, MRP_PROTO_ACTION_STOP_leavetimer),
            MRP_STATE_MACHINE_SET_UP_STATE_2 (MRP_REGISTRAR_STATE_IN, MRP_PROTO_ACTION_Join) },
    [MRP_PROTO_EVENT_rJoinMt]
        = { MRP_STATE_MACHINE_SET_UP_STATE_1 (MRP_REGISTRAR_STATE_IN),
            MRP_STATE_MACHINE_SET_UP_STATE_2 (MRP_REGISTRAR_STATE_IN, MRP_PROTO_ACTION_STOP_leavetimer),
            MRP_STATE_MACHINE_SET_UP_STATE_2 (MRP_REGISTRAR_STATE_IN, MRP_PROTO_ACTION_Join) },
    [MRP_PROTO_EVENT_rLv]
        = { MRP_STATE_MACHINE_SET_UP_STATE_2 (MRP_REGISTRAR_STATE_LV, MRP_PROTO_ACTION_START_leavetimer),
            MRP_STATE_MACHINE_SET_UP_STATE_1 (MRP_REGISTRAR_STATE_NO_CHANGE),
            MRP_STATE_MACHINE_SET_UP_STATE_1 (MRP_REGISTRAR_STATE_NO_CHANGE) },
    [MRP_PROTO_EVENT_rLA]
        = { MRP_STATE_MACHINE_SET_UP_STATE_2 (MRP_REGISTRAR_STATE_LV, MRP_PROTO_ACTION_START_leavetimer),
            MRP_STATE_MACHINE_SET_UP_STATE_1 (MRP_REGISTRAR_STATE_NO_CHANGE),
            MRP_STATE_MACHINE_SET_UP_STATE_1 (MRP_REGISTRAR_STATE_NO_CHANGE) },
    [MRP_PROTO_EVENT_txLA]
        = { MRP_STATE_MACHINE_SET_UP_STATE_2 (MRP_REGISTRAR_STATE_LV, MRP_PROTO_ACTION_START_leavetimer),
            MRP_STATE_MACHINE_SET_UP_STATE_1 (MRP_REGISTRAR_STATE_NO_CHANGE),
            MRP_STATE_MACHINE_SET_UP_STATE_1 (MRP_REGISTRAR_STATE_NO_CHANGE) },
    [MRP_PROTO_EVENT_ReDeclare]
        = { MRP_STATE_MACHINE_SET_UP_STATE_2 (MRP_REGISTRAR_STATE_LV, MRP_PROTO_ACTION_START_leavetimer),
            MRP_STATE_MACHINE_SET_UP_STATE_1 (MRP_REGISTRAR_STATE_NO_CHANGE),
            MRP_STATE_MACHINE_SET_UP_STATE_1 (MRP_REGISTRAR_STATE_NO_CHANGE) },
    [MRP_PROTO_EVENT_Flush]
        = { MRP_STATE_MACHINE_SET_UP_STATE_1 (MRP_REGISTRAR_STATE_MT),
            MRP_STATE_MACHINE_SET_UP_STATE_2 (MRP_REGISTRAR_STATE_MT, MRP_PROTO_ACTION_Lv),
            MRP_STATE_MACHINE_SET_UP_STATE_1 (MRP_REGISTRAR_STATE_MT) },
    [MRP_PROTO_EVENT_leavetimer]
        ={ 	MRP_STATE_MACHINE_SET_UP_STATE_1 (MRP_REGISTRAR_STATE_NO_CHANGE),
            MRP_STATE_MACHINE_SET_UP_STATE_2 (MRP_REGISTRAR_STATE_MT, MRP_PROTO_ACTION_Lv),
            MRP_STATE_MACHINE_SET_UP_STATE_1 (MRP_REGISTRAR_STATE_MT) }
};

/**
*   Get the state based on the supplied event and state
*/
static int mrp_get_registrar_state (int mrp_protocol_event, int mrp_registrar_state, int * mrp_new_registrar_state)
{
    int res;

    if ( (res = mrp_get_new_state(mrp_registrar_state_table, mrp_protocol_event, MRP_PROTO_EVENT_MIN, MRP_PROTO_EVENT_MAX, mrp_registrar_state,
            MRP_REGISTRAR_STATE_MIN, MRP_REGISTRAR_STATE_MAX, MRP_REGISTRAR_STATE_NO_CHANGE)) >= 0)
    {
        *mrp_new_registrar_state = res;
        return 0;
    }

    return res;
}

/**
*   Get the protocol action based on the supplied event and state
*/
static int mrp_get_registrar_protocol_action1 (int mrp_protocol_event, int mrp_registrar_state, int * mrp_new_registrar_protocol_action)
{
    int res;

    if ( (res = mrp_get_new_protocol_action1 (mrp_registrar_state_table, mrp_protocol_event, MRP_PROTO_EVENT_MIN, MRP_PROTO_EVENT_MAX, mrp_registrar_state,
            MRP_REGISTRAR_STATE_MIN, MRP_REGISTRAR_STATE_MAX)) >= 0)
    {
        *mrp_new_registrar_protocol_action = res;
        return 0;
    }

    return res;
}

/**
*   Get the protocol action based on the supplied event and state
*/
static int mrp_get_registrar_protocol_action2 (int mrp_protocol_event, int mrp_registrar_state, int * mrp_new_registrar_protocol_action)
{
    int res;

    if ( (res = mrp_get_new_protocol_action2 (mrp_registrar_state_table, mrp_protocol_event, MRP_PROTO_EVENT_MIN, MRP_PROTO_EVENT_MAX, mrp_registrar_state,
            MRP_REGISTRAR_STATE_MIN, MRP_REGISTRAR_STATE_MAX)) >= 0)
    {
        *mrp_new_registrar_protocol_action = res;
        return 0;
    }

    return res;
}

static int mrp_registrar_start_leavetimer (struct mrp_registrar * mrp_registrar)
{
    if (!timer_pending (&mrp_registrar->leavetimer))
    {
        mrp_registrar->leavetimer.expires = jiffies + MRP_TIMER_VALUE_LEAVE_TIME_AVERAGE_JIFFIES;
        add_timer (&(mrp_registrar->leavetimer));
    }
    return 0;
}

static int mrp_registrar_stop_leavetimer  (struct mrp_registrar * mrp_registrar)
{
    del_timer_sync (&(mrp_registrar->leavetimer));
    return 0;
}

static void mrp_registrar_leavetimer_function (unsigned long data)
{
    mrp_attribute_registrar_leave_timer_expire_notify (((struct mrp_registrar *) data)->mrp_attribute);
}

static int mrp_registrar_perform_protocol_action (struct mrp_registrar * mrp_registrar, int protocol_action)
{
    switch (protocol_action)
    {
        case MRP_PROTO_ACTION_New:				return mrp_attribute_perform_mad_join_indication 	(mrp_registrar->mrp_attribute, 1);
        case MRP_PROTO_ACTION_Join:				return mrp_attribute_perform_mad_join_indication 	(mrp_registrar->mrp_attribute, 0);
        case MRP_PROTO_ACTION_Lv:				return mrp_attribute_perform_mad_leave_indication 	(mrp_registrar->mrp_attribute);
        case MRP_PROTO_ACTION_START_leavetimer:	return mrp_registrar_start_leavetimer 	(mrp_registrar);
        case MRP_PROTO_ACTION_STOP_leavetimer:	return mrp_registrar_stop_leavetimer 	(mrp_registrar);
        default:
        {
            MRP_PRINT_INFO ("%s %x\n", "UNSUPPORTED PROTO ACTION:", protocol_action);
            return -EINVAL;
        }
    }
}

static struct mrp_state_machine_ops mrp_registrar_ops = {
    .mrp_state_machine_perform_protocol_action = (mrp_state_machine_perform_protocol_action) mrp_registrar_perform_protocol_action,
    .mrp_state_machine_get_new_state = mrp_get_registrar_state,
    .mrp_state_machine_get_new_protocol_action1 = mrp_get_registrar_protocol_action1,
    .mrp_state_machine_get_new_protocol_action2 = mrp_get_registrar_protocol_action2,
};

struct mrp_registrar * mrp_registrar_create_and_init (struct mrp_attribute * mrp_attribute)
{
    struct mrp_registrar * mrp_r;

    if (   ! (mrp_r = kzalloc (sizeof(struct mrp_registrar), MRP_GET_CONTEXT_ALLOC_FLAG))
        || 	 mrp_state_machine_init ((struct mrp_state_machine *)mrp_r, MRP_REGISTRAR_STATE_MT, &mrp_registrar_ops, "mrp_registrar"))
    {
        mrp_registrar_destroy (&mrp_r);
        return NULL;
    }

    setup_timer (&(mrp_r->leavetimer), mrp_registrar_leavetimer_function, (unsigned long) mrp_r);
    mrp_r->registrar_administrative_control = MRP_REGISTRAR_ADMINISTRATIVE_CONTROL_NORMAL_REGISTRATION; // the default value
    mrp_r->mrp_attribute = mrp_attribute;

    return mrp_r;
}

void mrp_registrar_destroy (struct mrp_registrar ** mrp_registrar)
{
    struct mrp_registrar * r;
    
    r = *mrp_registrar;
    
    if (r)
    {
        del_timer_sync (&r->leavetimer);
        mrp_state_machine_clean_up ((struct mrp_state_machine *)r);
        kfree (r);
        *mrp_registrar = NULL;
    }
}

int mrp_registrar_is_registered (struct mrp_registrar * mrp_registrar)
{
    return mrp_state_machine_get_current_state ((struct mrp_state_machine *) mrp_registrar) == MRP_REGISTRAR_STATE_IN;
}

int mrp_registrar_is_leaving (struct mrp_registrar * mrp_registrar)
{
    return mrp_state_machine_get_current_state ((struct mrp_state_machine *) mrp_registrar) ==  MRP_REGISTRAR_STATE_LV;
}

int mrp_registrar_is_empty (struct mrp_registrar * mrp_registrar)
{
    return mrp_state_machine_get_current_state ((struct mrp_state_machine *) mrp_registrar) == MRP_REGISTRAR_STATE_MT;
}

int mrp_registrar_set_registrar_administrative_control (struct mrp_registrar * mrp_registrar, int registrar_administrative_control)
{
    if (registrar_administrative_control >= MRP_REGISTRAR_ADMINISTRATIVE_CONTROL_MIN && registrar_administrative_control <= MRP_REGISTRAR_ADMINISTRATIVE_CONTROL_MAX)
    {
        mrp_registrar->registrar_administrative_control = registrar_administrative_control;
        return 0;
    }

    return -EINVAL;
}
